package edu.isistan.matching.utils.serviceMatching;

import java.util.List;
import java.util.Vector;

import edu.isistan.matching.adapters.AdapterFactory;
import edu.isistan.matching.adapters.PrimitiveTypeAdapter;
import edu.isistan.matching.filters.io.MatchingMap;
import edu.isistan.matching.scoreTypes.ComplexTypeMatchScore;
import edu.isistan.matching.scoreTypes.ComposeMatchScore;
import edu.isistan.matching.scoreTypes.MatchScore;
import edu.isistan.matching.scoreTypes.MessageMatchScore;
import edu.isistan.matching.scoreTypes.OperationMatchScore;
import edu.isistan.matching.scoreTypes.PrimitiveTypeMatchScore;
import edu.isistan.matching.scoreTypes.ScoreMatrix;
import edu.isistan.matching.scoreTypes.InterfaceMatchScore;
import edu.isistan.matching.utils.KuhnsHungarianMethod;
import edu.isistan.metamodel.Collection;
import edu.isistan.metamodel.Composed;
import edu.isistan.metamodel.DataType;
import edu.isistan.metamodel.Interface;
import edu.isistan.metamodel.Operation;

public abstract class AbstractMatching extends ServiceSimilarity {
	
	protected MatchingMap matchInterfaces(Interface inf0, Interface inf1) {
		List<Operation> ops0 = inf0.getOperations();
		List<Operation> ops1 = inf1.getOperations();
		int m = ops0.size();
		int n = ops1.size();

		ScoreMatrix operationMatrix = new ScoreMatrix(m, n);

		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				Operation opElem1 = ops0.get(i);
				Operation opElem2 = ops1.get(j);
				MatchScore opScore = matchOperations(opElem1, opElem2);
				operationMatrix.addValue(i, j, opScore);
			}
		}
		InterfaceMatchScore wsMatchScore = new InterfaceMatchScore();
		wsMatchScore.setChildScoreMatrix(operationMatrix);
		KuhnsHungarianMethod.findAssigment(wsMatchScore,false);

		printMatrix(operationMatrix.getMatrix(), m, n, "OPERATIONS:");
		printMatrix(operationMatrix.getMappingValues(), operationMatrix.getMappingValues().length, 2, "Correspondencias de operaciones Score total:" + wsMatchScore.getScore());

		retrieveWSDLElementsPairs(wsMatchScore, ops0, ops1);
		return wsMatchScore;
	}

	protected MatchScore matchOperations(Operation op0, Operation op1) {
		MatchScore msgInMatch = matchMessages(op0.getParameters(), op1.getParameters());
		MatchScore msgOutMatch = matchMessages(op0.getReturnValue(), op1.getReturnValue());

		OperationMatchScore opScore = new OperationMatchScore();
		double scoreValue = (msgInMatch.getScore() + msgOutMatch.getScore()) * OperationMessagesWeigth + analyzeOperationMatching(op0, op1) * OperationNamesWeigth;

		opScore.setScore(scoreValue);
		opScore.setMessageInMatchScore(msgInMatch);
		opScore.setMessageOutMatchScore(msgOutMatch);

		return opScore;
	}
	
	protected double matchPrimitiveTypes(DataType type0, DataType type1) {
		getLogger().debug("MATCH PRIMITIVE TYPES");
		PrimitiveTypeAdapter adapter = AdapterFactory.getAdapter(type0);
		getLogger().debug("ADAPTADOR: " + adapter.getClass());
		getLogger().debug("TYPE1:" + type0.getType());
		getLogger().debug("TYPE2:" + type1.getType());
		boolean sinPerdida = adapter.isFullyCompatible(type1);
		getLogger().debug("SIN PERDIDA: " + sinPerdida);
		if (sinPerdida) {
			return MAXSCORE;
		} else {
			boolean conPerdida = adapter.isLoosyCompatible(type1);
			getLogger().debug("CON PERDIDA: " + conPerdida);
			if (conPerdida) {
				return MEDIUM_SCORE;
			}
		}
		getLogger().debug("INCOMPATIBLE: true");
		return MINSCORE;
	}

	protected MatchScore matchMessages(List<DataType> inputs0, List<DataType> inputs1) {
		getLogger().debug("Matching Inputs: " + inputs0 + "-" + inputs1);
		if (inputs0 == null && inputs1 == null) {
			getLogger().debug("Ambos son nulos");
			return new MessageMatchScore(MAXSCORE);
		}
		if (inputs0 == null || inputs1 == null) {
			getLogger().debug("Uno es nulo.");
			return new MessageMatchScore(MINSCORE);
		}	
		List<DataType> dt0 = new Vector<DataType>(); 
		for (DataType dataType : inputs0) {
			dt0.addAll( aplanar(dataType) );
		}
		
		List<DataType> dt1 = new Vector<DataType>(); 
		for (DataType dataType : inputs1) {
			dt1.addAll( aplanar(dataType) );
		}
		
		MatchScore result = matchDataTypes(dt0, dt1);
		MessageMatchScore resultMjeScore = new MessageMatchScore();
		if (result.isComposeMatchScore()) {
			resultMjeScore.setChildScoreMatrix(((ComposeMatchScore) result).getChildScoreMatrix());
		} else {
			resultMjeScore.setComposeMatchScore(false);
		}
		resultMjeScore.setScore(result.getScore());
		resultMjeScore.setMessage1(inputs0);
		resultMjeScore.setMessage2(inputs1);

		return resultMjeScore;
	}
	
	protected MatchScore matchDataTypes(List<DataType> dataTypes0, List<DataType> dataTypes1) {
		int m = dataTypes0.size();
		int n = dataTypes1.size();
		if (m == 0 || n == 0) {
			getLogger().debug("Una lista de parámetros es vacía.");
			return new MatchScore(MINSCORE);
		}
		ScoreMatrix scoreMatrix = new ScoreMatrix(m, n);

		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				DataType type0 = dataTypes0.get(i);
				DataType type1 = dataTypes1.get(j);			
				if (someoneIsComplex(type0, type1)) {					
					getLogger().debug("Al menos uno es un objeto:\n" + type0 + " vs. " + type1);
					List<DataType> newSourceList = getCompositeDataElements(type0);
					List<DataType> newTargetList = getCompositeDataElements(type1);	
					MatchScore complexMatch = matchDataTypes(newSourceList, newTargetList);
					complexMatch.setScore(complexMatch.getScore());
					scoreMatrix.addValue(i, j, complexMatch);
				} else {
					getLogger().debug("Ambos son primitivos o wrappers de primitivos:\n" + type0 + " vs. " + type1);
					PrimitiveTypeMatchScore primitiveMatch = new PrimitiveTypeMatchScore(analyzePrimitivesMatching(type0, type1));
					primitiveMatch.setType1(type0);
					primitiveMatch.setType2(type1);
					scoreMatrix.addValue(i, j, primitiveMatch);
				} 
			}
		}
		printMatrix(scoreMatrix.getDoubleValuesMatrix(), m, n, "TYPES SCORE MATRIX:");

		ComplexTypeMatchScore resultadoMatch = new ComplexTypeMatchScore();
		resultadoMatch.setChildScoreMatrix(scoreMatrix);
		KuhnsHungarianMethod.findAssigment(resultadoMatch, true);

		printMatrix(resultadoMatch.getChildScoreMatrix().getMappingValues(), resultadoMatch.getChildScoreMatrix().getMappingValues().length, 2, "Pares Resultados:");
		retrieveWSDLElementsPairs(resultadoMatch, dataTypes0, dataTypes1);

		return resultadoMatch;
	}    
	
	protected List<DataType> aplanar(DataType dataType) {
		List<DataType> r = new Vector<DataType>();
		if (dataType.isCompoused()) {
			DataType child = null;
			if (dataType.isCollection() || dataType.isArray()) {
				child = ((Collection) dataType).getDataType();
			}
			if (child==null)
				child = dataType;
			for (DataType dataType2 : ((Composed) child).getDataTypes()) {
				r.addAll(aplanar(dataType2));
			}			
		} else {
			r.add(dataType);
		}
		return r;
	}

	protected double OperationMessagesWeigth = 0.5;
	
	protected double OperationNamesWeigth = 0.5;

	public void setOperationMessagesWeigth(double operationMessagesWeigth) {
		OperationMessagesWeigth = operationMessagesWeigth;
	}

	public void setOperationNamesWeigth(double operationNamesWeigth) {
		OperationNamesWeigth = operationNamesWeigth;
	}



	



	public abstract double analyzePrimitivesMatching(DataType tipo1, DataType tipo2);

	public abstract double analyzeOperationMatching(Operation op1, Operation op2);

}